Secrets Manager SQL Connection ドライバーを利用して JDBC で Java アプリケーションから Secrets Manager のローテーションされたパスワードを取得してみた

Secrets Manager SQL Connection ドライバーを利用して JDBC で Java アプリケーションから Secrets Manager のローテーションされたパスワードを取得してみた

Secrets Manager SQL Connection ドライバーを利用するとシークレットをキャッシュすることができ、ローテーションされたら新しいシークレットを取りにいく動作になります。Secrets Manager への API コールを減らすことができ、パフォーマンス向上とコスト削減効果が期待できます。
Clock Icon2024.07.25

コーヒーが好きな emi です。

Secrets Manager SQL Connection ドライバーを利用して JDBC で Java アプリケーションから Secrets Manager のローテーションされたパスワードを取得してみました。
私が Java アプリケーション初心者であるため冗長な案内もあるかもしれませんが、お付き合いいただければと思います。

自作サンプルコード

RDS for MySQL からテーブル内容を表示する簡単なサンプルです。Gradle でビルドして使います。

https://github.com/emi-ki/rds-java-secrets-test

  • build.gradle
    • 25 行目、30 行目周辺をご自身の環境に合わせて変更してください。
  • src/main/java/com/example/RdsInfoApp.java
    • 10、14、32 行目周辺をご自身の環境に合わせて変更してください。

解説

Java アプリケーションでは、Secrets Manager SQL Connection ドライバーを使用して、Secrets Manager に保存されている認証情報を使用して MySQL、PostgreSQL、Oracle、MSSQLServer、Db2、および Redshift データベースに接続できます。各ドライバーはベース JDBC ドライバーをラップしているため、JDBC 呼び出しを使用してデータベースにアクセスすることができます。

ドライバーは Java のクライアント側キャッシュライブラリを使用して認証情報をキャッシュするため、その後の接続では Secrets Manager を呼び出す必要はありません。デフォルトでは 1 時間ごと、およびシークレットがローテーションされたときに、キャッシュが更新されます。

これにより Secrets Manager への API コールを減らすことができ、パフォーマンス向上とコスト削減効果が期待できます。

https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/retrieving-secrets_jdbc.html#retrieving-secrets_jdbc_example_replica

公式のサンプルコードはこちらです。(自作サンプルコードはこれに気づかずに頑張って作ってしまいました)
https://github.com/aws/aws-secretsmanager-jdbc

検証イメージ図

以下のような構成で検証しました。

  • パブリックサブネットの EC2 インスタンス(RHEL9)に、RDS for MySQL のレコードを表示する .jar ファイルを配置
    • Secrets Manager SQL Connection ドライバーを使用して、Secrets Manager に保存されている usernamepassword を取得して RDS for MySQL にアクセス
    • .jar ファイルを実行して RDS for MySQL に設定済みの age_table に格納されたレコードを表示

emiki_java_secrets_1

以下のリソースはあらかじめ作成しておきます。

  • EC2
    • AMI ID:ami-0014871499315f25a
    • AMI 名:RHEL-9.3.0_HVM-20240117-x86_64-49-Hourly2-GP3
    • インスタンスタイプ:t3.medium
      • t3.micro だとビルド中に高負荷でダウンしてしまいました
    • EC2 の IAM ロールには SSM セッションマネージャーを使う権限と、RDS のシークレット ARN に対する"secretsmanager:GetSecretValue""secretsmanager:DescribeSecret" を許可する権限を付与

EC2 に付与する IAM ロールに付与する Secrets Manager へのアクセス許可は以下です。

カスタム管理ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret"
            ],
            "Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:<secrets name>"
        }
    ]
}
  • RDS
    • エンジン:MySQL Community
    • エンジンバージョン:8.0.36
    • クラス:db.t3.micro
    • 単一の RDS インスタンス

RDS for MySQL の中のデータベースとテーブルが以下のイメージです。

mysql> select * from age_table;
+----+------+------+
| id | name | age  |
+----+------+------+
|  1 | aoki |   20 |
|  2 | iida |   21 |
|  3 | usui |   22 |
+----+------+------+
3 rows in set (0.01 sec)

mysql>

emiki_java_secrets_3

補足

今回はパブリックサブネットに EC2 インスタンスを配置して実験しましたが、Java アプリケーションを配置するコンピュートサービスがプライベートサブネットに存在する場合は Secrets Manager にアクセスするのに VPC エンドポイントが必要です。Secrets Manager へのアクセス許可する VPC エンドポイントを作成し、VPC エンドポイントに設定するセキュリティグループで EC2 インスタンスからの HTTPS 443 アクセスを許可してください。
emiki_java_secrets_2

事前準備

検証のためのコマンドをインストール(オプション)

EC2 インスタンス(RHEL9)にあらかじめ mysql コマンドと git をインストールしておきました。

git バージョン確認コマンド

git --version

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ git --version
git version 2.43.5
[ec2-user@ip-10-0-0-26 ~]$

mysql バージョン確認コマンド

mysql --version

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ mysql --version
mysql  Ver 8.0.36 for Linux on x86_64 (Source distribution)
[ec2-user@ip-10-0-0-26 ~]$

RDS for MySQL 内のテーブル準備

あらかじめ RDS for MySQL にデータベースとテーブルを作成しておきました。今回は mysqldb というデータベースと age_tabel というテーブルを作成しています。

RDS for MySQL へのログイン(パスワード入力)

mysql -h database-1.xxxxx.ap-northeast-1.rds.amazonaws.com -P 3306 -u admin -p

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ mysql -h database-1.xxxxx.ap-northeast-1.rds.amazonaws.com -P 3306 -u admin -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 13
Server version: 8.0.36 Source distribution

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

存在するデータベースの確認

show databases;

▼実行結果

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| mysqldb            |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.05 sec)

mysql> 

データベースの選択

use mysqldb;

▼実行結果

mysql> use mysqldb;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql>
mysql> SELECT database();
+------------+
| database() |
+------------+
| mysqldb    |
+------------+
1 row in set (0.01 sec)

mysql> 

テーブル一覧の表示

show tables;

▼実行結果

mysql> show tables;
+-------------------+
| Tables_in_mysqldb |
+-------------------+
| age_table         |
+-------------------+
1 row in set (0.02 sec)

mysql> 

DB オブジェクトの定義情報を表示

desc age_table;

▼実行結果

mysql> desc age_table;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int         | NO   | PRI | NULL    |       |
| name  | varchar(45) | NO   |     | NULL    |       |
| age   | int         | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.03 sec)

mysql> 

age_table; の表示

select * from age_table;

▼実行結果

mysql> select * from age_table;
+----+------+------+
| id | name | age  |
+----+------+------+
|  1 | aoki |   20 |
|  2 | iida |   21 |
|  3 | usui |   22 |
+----+------+------+
3 rows in set (0.01 sec)

mysql>

EC2 インスタンス(RHEL9)をアップデートしておきます。

sudo dnf update -y

サンプルコードのクローン

冒頭で案内したサンプルコードを Github から EC2 インスタンス(RHEL9)にクローンしてきます。rds-java-secrets-test ディレクトリができれば OK です。

git clone [email protected]:emi-ki/rds-java-secrets-test.git

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ git clone [email protected]:emi-ki/rds-java-secrets-test.git
Cloning into 'rds-java-secrets-test'...
Enter passphrase for key '/home/ec2-user/.ssh/id_rsa':
Enter passphrase for key '/home/ec2-user/.ssh/id_rsa':
remote: Enumerating objects: 20, done.
remote: Counting objects: 100% (20/20), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 20 (delta 1), reused 17 (delta 1), pack-reused 0
Receiving objects: 100% (20/20), done.
Resolving deltas: 100% (1/1), done.
[ec2-user@ip-10-0-0-26 ~]$

ls -l コマンドで rds-java-secrets-test ディレクトリができているか確認します。

[ec2-user@ip-10-0-0-26 ~]$ ls -l
total 59416
drwxr-xr-x. 3 ec2-user ec2-user       78 Jun 11 18:31 aws
-rw-r--r--. 1 ec2-user ec2-user 60654157 Jun 12 06:37 awscliv2.zip
-rw-r--r--. 1 ec2-user ec2-user   183356 Jun 12 06:58 global-bundle.pem
drwxr-xr-x. 4 ec2-user ec2-user       66 Jul 23 12:09 rds-java-secrets-test
[ec2-user@ip-10-0-0-26 ~]$

ありますね。
では、クローンしてきた .java ファイルを自分の環境に合わせて修正します。

`src/main/java/com/example/RdsInfoApp.java` の中身
src/main/java/com/example/RdsInfoApp.java
package com.example;

import java.sql.*;
import java.util.Properties;
import com.amazonaws.secretsmanager.sql.AWSSecretsManagerMySQLDriver;

public class RdsInfoApp {
    public static void main(String[] args) {
        // エンドポイント、ポート、データベース名の設定
        String URL = "jdbc-secretsmanager:mysql://database-1.xxxxx.ap-northeast-1.rds.amazonaws.com:3306/<database name>";

        // userプロパティにsecret ARNを入力し、secretからユーザーとパスワードを取得
        Properties info = new Properties();
        info.put("user", "rds!db-xxxxxxxxxxxxxxxxxxxx");

        System.out.println("Attempting to connect to: " + URL);

        try {
            // Secrets Manager SQL Connection JDBC ドライバーをロード
            DriverManager.registerDriver(new AWSSecretsManagerMySQLDriver());

            // RDS for MySQL への接続
            try (Connection connection = DriverManager.getConnection(URL, info)) {
                System.out.println("Connection successful!");

                // データベースのメタデータを取得
                DatabaseMetaData metaData = connection.getMetaData();
                System.out.println("Connected as user: " + metaData.getUserName());

                // age_table にクエリ
                try (Statement statement = connection.createStatement();
                     ResultSet resultSet = statement.executeQuery("SELECT * FROM age_table")) {

                    ResultSetMetaData rsmd = resultSet.getMetaData();
                    int columnsNumber = rsmd.getColumnCount();

                    System.out.println("age_table の内容:");
                    // カラム名を出力
                    for (int i = 1; i <= columnsNumber; i++) {
                        if (i > 1) System.out.print(" | ");
                        System.out.print(rsmd.getColumnName(i));
                    }
                    System.out.println();

                    // テーブルの内容を出力
                    while (resultSet.next()) {
                        for (int i = 1; i <= columnsNumber; i++) {
                            if (i > 1) System.out.print(" | ");
                            System.out.print(resultSet.getString(i));
                        }
                        System.out.println();
                    }
                }
            }
        } catch (SQLException e) {
            System.err.println("Error occurred: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

src/main/java/com/example/RdsInfoApp.java の 10 行目で、自身が作成した RDS for MySQL のエンドポイントと、最初に作成したデータベース名を入れてください。今回はデータベース名 mysqldb としたので、mysqldb と入れておきます。
14 行目には RDS のシークレットの名前を入れてください。

build.gradle の中身も参考に貼っておきます。

`build.gradle` の中身
plugins {
    id 'java'
    id 'application'
}

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

repositories {
    mavenCentral()
    maven {
        url "https://aws.oss.sonatype.org/content/repositories/snapshots"
    }
}

dependencies {
    implementation 'mysql:mysql-connector-java:8.0.33'
    implementation 'com.amazonaws.secretsmanager:aws-secretsmanager-jdbc:2.0.2'
    implementation 'org.slf4j:slf4j-simple:1.7.32'
}

application {
    mainClass = 'com.example.RdsInfoApp' // アプリケーションのエントリポイント
}

jar {
    manifest {
        attributes 'Main-Class': 'com.example.RdsInfoApp'
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

25 行目 mainClass = 'com.example.RdsInfoApp' // アプリケーションのエントリポイント と、30 行目 attributes 'Main-Class': 'com.example.RdsInfoApp' をご自身の環境に合わせて変更してください。

EC2 インスタンスでの Java 実行環境準備

Gradle のインストール

ビルドツールである Gradle をダウンロードするために、wget コマンドをインストールしておきます。

sudo dnf install wget

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ sudo dnf install wget
Updating Subscription Management repositories.
Unable to read consumer identity

This system is not registered with an entitlement server. You can use "rhc" or "subscription-manager" to register.

Last metadata expiration check: 1:03:54 ago on Tue 23 Jul 2024 11:13:46 AM UTC.
Dependencies resolved.
============================================================================================================================================================================================
 Package                              Architecture                           Version                                       Repository                                                  Size
============================================================================================================================================================================================
Installing:
 wget                                 x86_64                                 1.21.1-7.el9                                  rhel-9-appstream-rhui-rpms                                 794 k

Transaction Summary
============================================================================================================================================================================================
Install  1 Package

Total download size: 794 k
Installed size: 3.1 M
Is this ok [y/N]: y
Downloading Packages:
wget-1.21.1-7.el9.x86_64.rpm                                                                                                                                 14 MB/s | 794 kB     00:00
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                                                       9.4 MB/s | 794 kB     00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                                                                                                                                    1/1
  Installing       : wget-1.21.1-7.el9.x86_64                                                                                                                                           1/1
  Running scriptlet: wget-1.21.1-7.el9.x86_64                                                                                                                                           1/1
  Verifying        : wget-1.21.1-7.el9.x86_64                                                                                                                                           1/1
Installed products updated.

Installed:
  wget-1.21.1-7.el9.x86_64

Complete!
[ec2-user@ip-10-0-0-26 ~]$

Gradle のどのバージョンをダウンロードするか確認しておきましょう。リリースバージョンは以下のサイトで確認します。

https://gradle.org/releases/

emiki_java_secrets_4

直近で v8.9 がリリースされていました。今回は 1 つ前の v8.8 をダウンロードすることにします。
ダウンロードリンクは以下のサイトで確認します。

https://services.gradle.org/distributions/

emiki_java_secrets_5

gradle-8.8-bin.zip をダウンロードすればよさそうです。

では Gradle をダウンロードします。

wget https://services.gradle.org/distributions/gradle-8.8-bin.zip

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ wget https://services.gradle.org/distributions/gradle-8.8-bin.zip
--2024-07-23 12:47:25--  https://services.gradle.org/distributions/gradle-8.8-bin.zip
Resolving services.gradle.org (services.gradle.org)... 104.16.73.101, 104.16.72.101, 2606:4700::6810:4965, ...
Connecting to services.gradle.org (services.gradle.org)|104.16.73.101|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://github.com/gradle/gradle-distributions/releases/download/v8.8.0/gradle-8.8-bin.zip [following]
--2024-07-23 12:47:25--  https://github.com/gradle/gradle-distributions/releases/download/v8.8.0/gradle-8.8-bin.zip
Resolving github.com (github.com)... 20.27.177.113
Connecting to github.com (github.com)|20.27.177.113|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/696192900/f26bf10a-b9bc-420a-927d-4eef13716428?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20240723%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240723T124725Z&X-Amz-Expires=300&X-Amz-Signature=4c96f6c6f73a01de14700456b425c74c415bd45f8db9269d9e886b838c923d47&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=696192900&response-content-disposition=attachment%3B%20filename%3Dgradle-8.8-bin.zip&response-content-type=application%2Foctet-stream[following]
--2024-07-23 12:47:26--  https://objects.githubusercontent.com/github-production-release-asset-2e65be/696192900/f26bf10a-b9bc-420a-927d-4eef13716428?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20240723%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240723T124725Z&X-Amz-Expires=300&X-Amz-Signature=4c96f6c6f73a01de14700456b425c74c415bd45f8db9269d9e886b838c923d47&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=696192900&response-content-disposition=attachment%3B%20filename%3Dgradle-8.8-bin.zip&response-content-type=application%2Foctet-stream
Resolving objects.githubusercontent.com (objects.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.108.133, ...
Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 138039528 (132M) [application/octet-stream]
Saving to: ‘gradle-8.8-bin.zip’

gradle-8.8-bin.zip                             100%[====================================================================================================>] 131.64M   109MB/s    in 1.2s

2024-07-23 12:47:27 (109 MB/s) - ‘gradle-8.8-bin.zip’ saved [138039528/138039528]

[ec2-user@ip-10-0-0-26 ~]$

[ec2-user@ip-10-0-0-26 ~]$ ls -lh gradle-8.8-bin.zip
-rw-r--r--. 1 ec2-user ec2-user 132M May 31 21:58 gradle-8.8-bin.zip
[ec2-user@ip-10-0-0-26 ~]$

Gradle を解凍するディレクトリを作成します。

sudo mkdir -p /opt/gradle

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ sudo mkdir -p /opt/gradle
[ec2-user@ip-10-0-0-26 ~]$ 

zip ファイルを解凍します。

sudo unzip -d /opt/gradle ~/gradle-8.8-bin.zip

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ sudo unzip -d /opt/gradle ~/gradle-8.8-bin.zip
Archive:  /home/ec2-user/gradle-8.8-bin.zip
   creating: /opt/gradle/gradle-8.8/
  inflating: /opt/gradle/gradle-8.8/LICENSE
  inflating: /opt/gradle/gradle-8.8/NOTICE
  inflating: /opt/gradle/gradle-8.8/README
   creating: /opt/gradle/gradle-8.8/init.d/
  inflating: /opt/gradle/gradle-8.8/init.d/readme.txt
  :
  :
  :
   creating: /opt/gradle/gradle-8.8/lib/agents/
  inflating: /opt/gradle/gradle-8.8/lib/agents/gradle-instrumentation-agent-8.8.jar
[ec2-user@ip-10-0-0-26 ~]$  

以下コマンドで、 Gradle の /opt/gradle/gradle-8.8/bin(Gradle のバイナリがあるディレクトリ)を PATH 環境変数に追加し、設定を .bashrc ファイルに保存します。

echo 'export PATH=$PATH:/opt/gradle/gradle-8.8/bin' >> ~/.bashrc

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ echo 'export PATH=$PATH:/opt/gradle/gradle-8.8/bin' >> ~/.bashrc
[ec2-user@ip-10-0-0-26 ~]$

設定を有効にするために、.bashrc ファイルを再読み込みします。

source ~/.bashrc

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ source ~/.bashrc
[ec2-user@ip-10-0-0-26 ~]$

これで、Gradle コマンドを任意のディレクトリから実行できるようになりました。

OpenJDK 17 のインストール

OpenJDK 17 をインストールします。

sudo dnf install java-17-openjdk-devel

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ sudo dnf install java-17-openjdk-devel
Updating Subscription Management repositories.
Unable to read consumer identity

This system is not registered with an entitlement server. You can use "rhc" or "subscription-manager" to register.

Last metadata expiration check: 1:40:33 ago on Tue 23 Jul 2024 11:13:46 AM UTC.
Dependencies resolved.
============================================================================================================================================================================================
 Package                                                   Architecture             Version                                              Repository                                    Size
============================================================================================================================================================================================
Installing:
 java-17-openjdk-devel                                     x86_64                   1:17.0.12.0.7-2.el9                                  rhel-9-appstream-rhui-rpms                   4.7 M
Installing dependencies:
 ModemManager-glib                                         x86_64                   1.20.2-1.el9                                         rhel-9-baseos-rhui-rpms                      337 k
 adobe-source-code-pro-fonts                               noarch                   2.030.1.050-12.el9.1                                 rhel-9-appstream-rhui-rpms                   836 k
 adwaita-cursor-theme    
:
:
:
tracker-miners                                            x86_64                   3.1.2-4.el9_3                                        rhel-9-appstream-rhui-rpms                   942 k
 xdg-desktop-portal-gtk                                    x86_64                   1.12.0-3.el9                                         rhel-9-appstream-rhui-rpms                   139 k

Transaction Summary
============================================================================================================================================================================================
Install  162 Packages

Total download size: 125 M
Installed size: 457 M
Is this ok [y/N]: y
Downloading Packages:
(1/162): copy-jdk-configs-4.0-3.el9.noarch.rpm                                                                                                              562 kB/s |  29 kB     00:00
(2/162): exiv2-libs-0.27.5-2.el9.x86_64.rpm                                                                                                                  12 MB/s | 782 kB     00:00
(3/162): gtk-update-icon-cache-3.24.31-2.el9.x86_64.rpm                                                                                                     2.2 MB/s |  37 kB     00:00
(4/162): exiv2-0.27.5-2.el9.x86_64.rpm                                                                                                                       12 MB/s | 984 kB     00:00
(5/162): libXcursor-1.2.0-7.el9.x86_64.rpm                                                                                                                  1.4 MB/s |  33 kB     00:00
(6/162): libXext-1.3.4-8.el9.x86_64.rpm
:
:
:
  xdg-dbus-proxy-0.1.3-1.el9.x86_64                           xdg-desktop-portal-1.12.6-1.el9.x86_64                           xdg-desktop-portal-gtk-1.12.0-3.el9.x86_64
  xkeyboard-config-2.33-2.el9.noarch                          xml-common-0.6.3-58.el9.noarch                                   xorg-x11-fonts-Type1-7.5-33.el9.noarch

Complete!
[ec2-user@ip-10-0-0-26 ~]$

インストールが完了したら、Java のバージョンを確認します。

java -version

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ java -version
openjdk version "17.0.12" 2024-07-16 LTS
OpenJDK Runtime Environment (Red_Hat-17.0.12.0.7-1) (build 17.0.12+7-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-17.0.12.0.7-1) (build 17.0.12+7-LTS, mixed mode, sharing)
[ec2-user@ip-10-0-0-26 ~]$

JAVA_HOME が正しく設定されていることを確認します。

echo $JAVA_HOME

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ echo $JAVA_HOME

[ec2-user@ip-10-0-0-26 ~]$

出力が空なので、設定されていないですね。設定していきます。
まず Java のインストールパスを確認します。

sudo update-alternatives --config java

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ sudo update-alternatives --config java

There is 1 program that provides 'java'.

  Selection    Command
-----------------------------------------------
*+ 1           java-17-openjdk.x86_64 (/usr/lib/jvm/java-17-openjdk-17.0.12.0.7-2.el9.x86_64/bin/java)

Enter to keep the current selection[+], or type selection number:
[ec2-user@ip-10-0-0-26 ~]$

Java 17 のインストールパスが "/usr/lib/jvm/java-17-openjdk-17.0.12.0.7-2.el9.x86_64" であることが確認できました。このパスを JAVA_HOME 環境変数に設定しましょう。

JAVA_HOME 環境変数として OpenJDK 17 のインストールパスを指定し、~/.bashrc ファイルに追加します。

echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-17.0.12.0.7-2.el9.x86_64' >> ~/.bashrc

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-17.0.12.0.7-2.el9.x86_64' >> ~/.bashrc
[ec2-user@ip-10-0-0-26 ~]$ 

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ echo 'export PATH=$PATH:$JAVA_HOME/bin' >> ~/.bashrc
[ec2-user@ip-10-0-0-26 ~]$

変更を反映させるために、以下のコマンドを実行します。

[ec2-user@ip-10-0-0-26 ~]$ source ~/.bashrc
[ec2-user@ip-10-0-0-26 ~]$

これにより、Java の実行ファイルをシステム全体から呼び出せるようになります。

JAVA_HOME が正しく設定されたか確認します。

[ec2-user@ip-10-0-0-26 ~]$ echo $JAVA_HOME
/usr/lib/jvm/java-17-openjdk-17.0.12.0.7-2.el9.x86_64
[ec2-user@ip-10-0-0-26 ~]$

JAVA_HOME 環境変数に "/usr/lib/jvm/java-17-openjdk-17.0.12.0.7-2.el9.x86_64" が設定できたことが確認できました。

Java コマンドが JAVA_HOME から実行されていることを確認します。

$JAVA_HOME/bin/java -version

▼実行結果

[ec2-user@ip-10-0-0-26 ~]$ $JAVA_HOME/bin/java -version
openjdk version "17.0.12" 2024-07-16 LTS
OpenJDK Runtime Environment (Red_Hat-17.0.12.0.7-1) (build 17.0.12+7-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-17.0.12.0.7-1) (build 17.0.12+7-LTS, mixed mode, sharing)
[ec2-user@ip-10-0-0-26 ~]$

JAVA_HOME から Java が実行できました。
OpenJDK version 17.0.12 の LTS(Long Term Support)バージョンが使用されています。
Red Hat によって提供されているビルドで、64 ビットの Server VM が使用されています。

Gradle が正しく Java を認識しているか確認するため、Gradle のバージョンを確認します。

[ec2-user@ip-10-0-0-26 ~]$ gradle --version

Welcome to Gradle 8.8!

Here are the highlights of this release:
 - Running Gradle on Java 22
 - Configurable Gradle daemon JVM
 - Improved IDE performance for large projects

For more details see https://docs.gradle.org/8.8/release-notes.html

------------------------------------------------------------
Gradle 8.8
------------------------------------------------------------

Build time:   2024-05-31 21:46:56 UTC
Revision:     4bd1b3d3fc3f31db5a26eecb416a165b8cc36082

Kotlin:       1.9.22
Groovy:       3.0.21
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          17.0.12 (Red Hat, Inc. 17.0.12+7-LTS)
OS:           Linux 5.14.0-362.18.1.el9_3.x86_64 amd64

[ec2-user@ip-10-0-0-26 ~]$

Gradle バージョンは 8.8、使用している JVM[1] のバージョンは 17.0.12 (Red Hat, Inc. 17.0.12+7-LTS) であり、インストールした Java のバージョンと一致しています。Gradle が正しくインストールされ、Java 17 を正しく認識していることが確認できました。

プロジェクトのビルドと実行

プロジェクトディレクトリに移動します。

[ec2-user@ip-10-0-0-26 ~]$ cd rds-java-secrets-test/
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$

ドキュメントや公式サンプルでは Maven (pom.xml) を使っていますが、私の自作サンプルでは Gradle を使っています。
詳細は Maven Central Repository を参照してください。
https://central.sonatype.com/artifact/com.amazonaws.secretsmanager/aws-secretsmanager-jdbc?smo=true

スニペットは Gradle (short) を使用します。Gradle(short) は Gradle プロジェクトで依存関係を追加する際の簡潔な記法です。

プロジェクトをビルドします。

gradle build --refresh-dependencies

▼実行結果

[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$ gradle build --refresh-dependencies

BUILD SUCCESSFUL in 11s
5 actionable tasks: 5 executed
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$

BUILD SUCCESSFUL と表示され、ビルドが成功しました。
生成された JAR ファイルを確認します

[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$ ls -l
total 8
drwxr-xr-x. 8 ec2-user ec2-user  97 Jul 23 16:00 build
-rw-r--r--. 1 ec2-user ec2-user 828 Jul 23 15:59 build.gradle
-rw-r--r--. 1 ec2-user ec2-user  46 Jul 23 12:09 README.md
drwxr-xr-x. 3 ec2-user ec2-user  18 Jul 23 12:09 src
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$ ls -l build
total 0
drwxr-xr-x. 3 ec2-user ec2-user 18 Jul 23 15:59 classes
drwxr-xr-x. 2 ec2-user ec2-user 72 Jul 23 16:00 distributions
drwxr-xr-x. 3 ec2-user ec2-user 21 Jul 23 15:59 generated
drwxr-xr-x. 2 ec2-user ec2-user 39 Jul 23 15:59 libs
drwxr-xr-x. 2 ec2-user ec2-user 68 Jul 23 16:00 scripts
drwxr-xr-x. 5 ec2-user ec2-user 50 Jul 23 15:59 tmp
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$ ls -l build/libs/
total 15160
-rw-r--r--. 1 ec2-user ec2-user 15523380 Jul 23 16:00 rds-java-secrets-test.jar
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$

build/libs/ 配下にビルドされた JAR ファイル rds-java-secrets-test.jar があることが確認できます。
JAR ファイルを実行してアプリケーションをテストします。
JAR ファイルは java -jar build/libs/[生成されたJARファイル名].jar のように実行します。

java -jar build/libs/rds-java-secrets-test.jar

▼実行結果

[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$ java -jar build/libs/rds-java-secrets-test.jar
Attempting to connect to: jdbc-secretsmanager:mysql://database-1.xxxxx.ap-northeast-1.rds.amazonaws.com:3306/mysqldb
Connection successful!
Connected as user: [email protected]
age_table の内容:
id | name | age
1 | aoki | 20
2 | iida | 21
3 | usui | 22
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$

RDS for MySQL から age_table の内容が表示できました。

シークレットをローテーションする

今回私は RDS が自動で作成するマスターユーザーのシークレットで検証したので、RDS 側からシークレット(usernamepassword)を変更します。

emiki_java_secrets_6

emiki_java_secrets_7

emiki_java_secrets_8

emiki_java_secrets_9

emiki_java_secrets_10

今回は RDS 側から変更しましたが、別途 MySQL 用ユーザーを作成しパスワードを自動ローテーションさせている場合は Secrets Manager 側の「すぐにシークレットをローテーションさせる」からローテーションさせることができます。
emiki_java_secrets_11

[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$ java -jar build/libs/rds-java-secrets-test.jar
Attempting to connect to: jdbc-secretsmanager:mysql://database-1.xxxxx.ap-northeast-1.rds.amazonaws.com:3306/mysqldb
Connection successful!
Connected as user: [email protected]
age_table の内容:
id | name | age
1 | aoki | 20
2 | iida | 21
3 | usui | 22
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$

ローテーションしてもちゃんとデータが取れます。再ログインしても大丈夫です。

sh-5.1$ sudo su - ec2-user
Last login: Tue Jul 23 17:26:24 UTC 2024 on pts/2
[ec2-user@ip-10-0-0-26 ~]$
[ec2-user@ip-10-0-0-26 ~]$ cd rds-java-secrets-test/
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$ java -jar build/libs/rds-java-secrets-test.jar
Attempting to connect to: jdbc-secretsmanager:mysql://database-1.xxxxx.ap-northeast-1.rds.amazonaws.com:3306/mysqldb
Connection successful!
Connected as user: [email protected]
age_table の内容:
id | name | age
1 | aoki | 20
2 | iida | 21
3 | usui | 22
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$

では RDS に直接ログインして age_table を変更します。

mysql> INSERT INTO age_table (id, name, age) VALUES (4, 'eguchi', 23);
Query OK, 1 row affected (0.01 sec)

mysql> select * from age_table;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | aoki   |   20 |
|  2 | iida   |   21 |
|  3 | usui   |   22 |
|  4 | eguchi |   23 |
+----+--------+------+
4 rows in set (0.00 sec)

mysql> INSERT INTO age_table (id, name, age) VALUES (5, 'okumura', 24);
Query OK, 1 row affected (0.00 sec)

mysql> select * from age_table;
+----+---------+------+
| id | name    | age  |
+----+---------+------+
|  1 | aoki    |   20 |
|  2 | iida    |   21 |
|  3 | usui    |   22 |
|  4 | eguchi  |   23 |
|  5 | okumura |   24 |
+----+---------+------+
5 rows in set (0.00 sec)

mysql> exit
Bye
sh-5.1$ exit

再度シークレットをローテーションします。

emiki_java_secrets_7

もう一度 EC2 にログインして jar アプリを動かします。

sh-5.1$ sudo su - ec2-user
Last login: Tue Jul 23 18:00:56 UTC 2024 on pts/0
[ec2-user@ip-10-0-0-26 ~]$ cd rds-java-secrets-test/
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$ java -jar build/libs/rds-java-secrets-test.jar
Attempting to connect to: jdbc-secretsmanager:mysql://database-1.xxxxx.ap-northeast-1.rds.amazonaws.com:3306/mysqldb
Connection successful!
Connected as user: [email protected]
age_table の内容:
id | name | age
1 | aoki | 20
2 | iida | 21
3 | usui | 22
4 | eguchi | 23
5 | okumura | 24
[ec2-user@ip-10-0-0-26 rds-java-secrets-test]$

データ変更してシークレットをローテーションした後も、ちゃんとレコードが取れました。いいですねぇ。

後でやりたいこと

以下も検証したいと思っています。続報をお待ちください。

  • プライベートサブネットの EC2 インスタンスからの実行
  • シークレットのキャッシュ更新間隔を変更

参考

https://gradle.org/releases/

https://services.gradle.org/distributions/

https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/rotate-secrets_managed.html

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-secrets-manager.html#rds-secrets-manager-db-instance

https://docs.aws.amazon.com/ja_jp/sdk-for-java/v1/developer-guide/credentials.html

https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/auth-and-access_examples.html#auth-and-access_examples-read-and-describe

脚注
  1. Java のプログラムを動かすために必要なソフトウェア。 https://wa3.i-3-i.info/word12706.html ↩︎

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.